Skip to content

perf: reduce snapshot read memory retention#985

Merged
zxch3n merged 70 commits into
mainfrom
feat/optimize-snapshot-read-memory
May 25, 2026
Merged

perf: reduce snapshot read memory retention#985
zxch3n merged 70 commits into
mainfrom
feat/optimize-snapshot-read-memory

Conversation

@lodyai

@lodyai lodyai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Avoid retaining decoded snapshot values/states for lazy containers during read-only traversal.
  • Route Map/List/MovableList handler reads through no-retain read helpers used by toJSON and loro-mirror style reads.
  • Keep root discovery lazy and avoid repeated root KV scans.
  • Include compact internal representation work for container store, list/map state, and richtext snapshot loading.

Measurements

Using the 32 MB fixture probe after exporting/importing a snapshot:

  • toJSON() then dropping JSON: 41.81 MB -> 25.58 MB
  • mirror shallow read: 41.81 MB -> 25.58 MB
  • mirror schema root read: 43.79 MB -> 25.58 MB
  • after a few inserts on the snapshot doc: 30.34 MB

Tradeoff: repeated full read-only traversals decode again instead of reusing retained decoded values. Current release probe timings were roughly toJSON 72 ms, mirror shallow 79/59 ms, and mirror schema root 143/131 ms.

Validation

  • cargo check -p loro-internal
  • cargo test -p loro-internal handler --lib
  • cargo test -p loro-internal map --lib
  • cargo test -p loro-internal list --lib
  • cargo test -p loro-internal snapshot --lib
  • cargo test -p loro-internal richtext --lib
  • git diff --check

@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

WASM Size Report

  • Original size: 2999.99 KB
  • Gzipped size: 990.48 KB
  • Brotli size: 695.90 KB

@lodyai lodyai Bot force-pushed the feat/optimize-snapshot-read-memory branch from 65d6c6e to 6f0cbaa Compare May 22, 2026 16:50
@zxch3n zxch3n merged commit 8f57f4c into main May 25, 2026
1 check passed
zxch3n added a commit that referenced this pull request Jun 17, 2026
* fix: recover two per-op editing slowdowns regressed since 1.11

Both are constant-factor regressions on the per-op (auto-commit) editing
path introduced by the lazy-snapshot work in #985, found while bisecting
current HEAD against the 1.11.1 release.

1. map/list/movable-list insert: ensure_no_regular_container_value
   heap-allocated a Vec on every insert, even for scalar values (the
   common case). Add a scalar fast-path that skips the allocation and
   traversal. `map create 10^4 key`: ~19.4ms -> ~10.7ms.

2. text insert/delete: the per-op bounds-check len()/len_unicode()/
   len_utf16() took two DocState locks (decoded-check, then query).
   Consolidate into one DocState::get_text_len taking a single lock +
   container-store lookup, preserving the lazy-snapshot memory behavior
   (a still-lazy container reads cached length metadata without
   materializing the richtext state). `bench_text B4 apply`: ~389ms -> ~352ms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* chore: sync Cargo.lock with loro-wasm 1.13.4 version bump

Lockfile update to match the crates/loro-wasm Cargo.toml version bump that
landed via the main merge (chore: version packages).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix: read cached byte length for PosType::Bytes text len

Addresses a review finding on the per-op text length consolidation: the
PosType::Bytes branch of DocState::get_text_len built the full plain string
via get_value_by_idx().as_string().len(), so insert_utf8/delete_utf8 bounds
checks on an already-decoded text became O(text length) per op (the old code
read cached byte-length metadata in the decoded case).

Add a text_utf8_len store helper mirroring text_unicode_len/text_utf16_len:
- decoded state reads the O(1) cached byte length
  (RichtextState::len_utf8 = root_cache().bytes)
- a still-lazy container reads the byte length from the already-materialized
  text value string (O(1) str::len), preserving the no-force-decode behavior

Also route the public TextHandler::len_utf8 through the same single-lock
helper; it had the same has_decoded_state double-lock + string-construction
pattern.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant